// Copyright 2004 Gary Baumgartner

// gcc -Wall -pedantic taskbar.cc gfblib.o -o edgy -L/usr/X11R6/lib -lX11 -lXtst -lstdc++ 2>&1 | more
//   production: -O2
//   more warnings: -Weffc++

// TODO
//   **Isolate and document configuration parameters.
//
//   *Try out submenus.
//   *Option to activate gesture menu by press (not release).
//   *Clean up trigger handling.
//
//   Convert to no-interceptor grabbing.
//   Play more with, e.g., scroll activates menu, scroll / multiclick through window shapes.
//
//   Provide for passing mouse position, focus window to system command.

using namespace std;

#include <stdlib.h> // system(), EXIT_FAILURE
#include <string.h> // strlen(), strcmp()
#include <math.h>
// #include <unistd.h> // sleep()
// #include <iostream> // cout, endl

#include "edgy.h"

#ifndef OLD_CONFIG
#include "configparser.h"
#endif

#include <iostream>

Display* dpy;
int screen;
Window root;
//
// Make typical connection to X server, record default screen and root window.
//
void* init_X() {
  dpy = XOpenDisplay(NULL);
  screen = DefaultScreen(dpy);
  root = DefaultRootWindow(dpy);
  return NULL;
}
void* init_x = init_X();

/*** GENERAL X HELPER FUNCTIONS ***/

// Tell window manager to leave w alone.
//
void override_redirect(Window w) {
  XSetWindowAttributes att;
  att.override_redirect = True;
  XChangeWindowAttributes(dpy, w, CWOverrideRedirect, &att);
}

// Currently focused window
//
Window focus_window() {
  Window result;
  int dummy_int;
  XGetInputFocus(dpy, &result, &dummy_int);
  return result;
}

// Move pointer to (x, y) relative to w.
//   w == None same as w == root.
void warp_pointer(Window w, int x, int y) {
  XWarpPointer(dpy, None, w, 0, 0, 0, 0, x, y);
}

void fake_key(KeySym ks, bool press) {
  if (ks != NoSymbol) XTestFakeKeyEvent(dpy, XKeysymToKeycode(dpy, ks), press, 0);
}

// Whether currently focused window's class is string c.
//
//   Windows typically have
//     name: WM_NAME, XFetchName
//     application: WM_CLASS first component
//     application class: WM_CLASS second component
//
// [Cache the info?]
//
bool focus_class_is(const char* c) {
  bool result = false;
  Window w = focus_window();
  XClassHint ch;
  if (XGetClassHint(dpy, w, &ch)) {
    result = ch.res_class && !strcmp(c, ch.res_class);
    if (ch.res_name) XFree(ch.res_name);
    if (ch.res_class) XFree(ch.res_class);
  }
  return result;
}

// Make simple attempt to provide provide color,
//  regardless of visual type of screen.
//
// color: see man XLookupColor for naming options
// return: pixel number of color
//
unsigned long make_color(const char* color) {
  Colormap colormap = DefaultColormap(dpy, screen);
  XColor dummy_xcolor;
  XColor return_xcolor;
  XLookupColor(dpy, colormap, color, &dummy_xcolor, &return_xcolor);
  XAllocColor(dpy, colormap, &return_xcolor);
  return return_xcolor.pixel;
}



struct Geometry {
  int absolute_x, absolute_y;
  int relative_x, relative_y;
  unsigned int inside_width, inside_height;
  unsigned int total_width, total_height;
  unsigned int border_width;
};

Geometry geometry(Window w) {
  Geometry result;
  Window dummy_window;
  unsigned int dummy_unsigned_int;
  XGetGeometry(dpy, w,
               &dummy_window,
               &result.relative_x, &result.relative_y,
               &result.inside_width, &result.inside_height, &result.border_width,
               &dummy_unsigned_int);
  XTranslateCoordinates(dpy, w, root, 0, 0,
			&result.absolute_x, &result.absolute_y, &dummy_window);
  result.total_width = result.inside_width + 2 * result.border_width;
  result.total_height = result.inside_height + 2 * result.border_width;
  return result;
}


/*** Location ***/

// [Would adding const to parameters allow compiler optimizations?]
// What's the heuristic for parameter const T& ?

pair<int, int> upper(pair<int, int> coord) {
  coord.second = 0;
  return coord;
}
pair<int, int> lower(pair<int, int> coord) {
  coord.second = DisplayHeight(dpy, screen) - 1;
  return coord;
}
pair<int, int> left(pair<int, int> coord) {
  coord.first = 0;
  return coord;
}
pair<int, int> right(pair<int, int> coord) {
  coord.first = DisplayWidth(dpy, screen) - 1;
  return coord;
}

/*** Place ***/

// Don't call this, use the constants below instead.
Place::Place(int h_sign, int v_sign, string name="") : h_sign(h_sign), v_sign(v_sign), sym_name(name) {}

bool Place::operator==(const Place& r) const {
  return h_sign == r.h_sign && v_sign == r.v_sign;
}

bool Place::contains(const pair<int, int>& coords) const { // it worried that simply pair<int, int> might mutate
  return (h_sign >= 0 || coords == left(coords))
    && (h_sign <= 0 || coords == right(coords))
    && (v_sign >= 0 || coords == upper(coords))
    && (v_sign <= 0 || coords == lower(coords));
}
bool Place::contains(const Place& r) const {
  return (h_sign == 0 || h_sign == r.h_sign) && (v_sign == 0 || v_sign == r.v_sign);
}

pair<int, int> Place::position() const {
  return pair<int, int>(h_sign < 1 ? 0 : DisplayWidth(dpy, screen) - 1,
      v_sign < 1 ? 0 : DisplayHeight(dpy, screen) - 1);
}

pair<int, int> Place::dimensions() const {
  return pair<int, int>(h_sign == 0 && v_sign != 0 ? DisplayWidth(dpy, screen) : 1,
      h_sign != 0 && v_sign == 0 ? DisplayHeight(dpy, screen) : 1);
}

string Place::name() const { return sym_name; }

const Place TOP(0, -1, "top");
const Place BOTTOM(0, 1, "bottom");
const Place LEFT(-1, 0, "left");
const Place RIGHT(1, 0, "right");
const Place TOP_LEFT(-1, -1, "top left");
const Place TOP_RIGHT(1, -1, "top right");
const Place BOTTOM_LEFT(-1, 1, "bottom left");
const Place BOTTOM_RIGHT(1, 1, "bottom right");

pair<int, int> interior_delta(pair<int, int> mouse_position) {
  pair<int, int> result(0, 0);
  if (LEFT.contains(mouse_position)) ++result.first;
  if (RIGHT.contains(mouse_position)) --result.first;
  if (TOP.contains(mouse_position)) ++result.second;
  if (BOTTOM.contains(mouse_position)) --result.second;
  return result;
}

/*** Trigger ***/

// [Because of pointer member, in theory should provide
//  copy constructor, assignment operator, destructor.
//  Consider it the owner of focus_class]
//
Trigger::Trigger(unsigned int button, Place p, char* focus_class = NULL) :
  b(button), p(p), focus_class(focus_class) {}
bool Trigger::applies(pair<int, int> mouse_position, unsigned int button) const {
  return button == this->b && p.contains(mouse_position) &&
    (focus_class == NULL || focus_class_is(focus_class));
}
Place Trigger::place() const { return p; }
unsigned int Trigger::button() const { return b; }
const char* Trigger::window_class() const { return focus_class; }


/*** ACTIONS ***/

void No_Action::act(pair<int, int> mouse_position, unsigned int button) {}

System_Action::System_Action(char* command) : cmd(string_append(command, " &", NULL)), clean_cmd(command) {}
void System_Action::act(pair<int, int> mouse_position, unsigned int button) { system(cmd); }
const char* System_Action::command() const { return clean_cmd; }

Sawfish_Action::Sawfish_Action(char* form) :
    System_Action(string_append("sawfish-client -e '", form,"' >/dev/null", NULL)) {}

Key_Action::Key_Action(KeySym k1, KeySym k2 = NoSymbol, KeySym k3 = NoSymbol): p(NULL), k1(k1), k2(k2), k3(k3) {}
Key_Action::Key_Action(Place p, KeySym k1, KeySym k2 = NoSymbol, KeySym k3 = NoSymbol): p(new Place(p)), k1(k1), k2(k2), k3(k3) {}
void Key_Action::act(pair<int, int> mouse_position, unsigned int button) {
  if (p) {
    // [Heuristic for init by = vs ()?]
    Window focus_w = focus_window();
    Geometry g = geometry(focus_w);
    int rx = LEFT.contains(*p) ? 0 : (RIGHT.contains(*p) ? g.total_width - 1 : g.total_width / 2 - 1);
    int ry = TOP.contains(*p) ? 0 : (BOTTOM.contains(*p) ? g.total_height - 1 : g.total_height / 2 - 1);
    warp_pointer(focus_w, rx, ry);
  }
  fake_key(k1, True); fake_key(k2, True); fake_key(k3, True);
  fake_key(k3, False); fake_key(k2, False); fake_key(k1, False);
}
const Place* Key_Action::place() { return p; }
const KeySym Key_Action::key1() { return k1; }
const KeySym Key_Action::key2() { return k2; }
const KeySym Key_Action::key3() { return k3; }

/*** MENU TYPES ***/

void Menu::act(pair<int, int> mouse_position, unsigned int button) {
  active_menu = this;
}

void Menu::begin_interaction(pair<int, int> mouse_position, unsigned int button) {
  XMapRaised(dpy, window);
  XGrabPointer(dpy, window,
   False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
   GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
  beginning_interaction(mouse_position, button);
}

void Menu::handle(XEvent* e) {
  if (e->xany.window == window) handling(e);
}

void Menu::beginning_interaction(pair<int, int> mouse_position, unsigned int button) {}

void Menu::end_interaction() {
  XUnmapWindow(dpy, window);
  XUngrabPointer(dpy, CurrentTime);
  active_menu = NULL;
}

//
Font Menu::font;
GC Menu::gc;
GC Menu::highlight_gc;
unsigned long Menu::background_color;
//
Menu* Menu::active_menu = NULL;


Vertical_Menu::Vertical_Menu(menu_item* items, unsigned int num_items)
  : num_items(num_items), m_items(items), menu_name("") {
  init();
}
Vertical_Menu::Vertical_Menu(menu_item* items, unsigned int num_items, const char* name=NULL)
  : num_items(num_items), m_items(items) {
  if (name) {
    int len = strlen(name);
    menu_name = (char*)malloc(len + 1);
    strcpy(menu_name, name);
    menu_name[len] = '\0';
  } else
    menu_name = "";
  init();
}
void Vertical_Menu::init() {
  // Calculate and record geometry based on font and configuration.
  //
  XFontStruct* fs = XQueryFont(dpy, font);
  //
  width = 0;
  for (unsigned int i = 0; i < num_items; ++i) {
    width = max(width, XTextWidth(fs, m_items[i].label, strlen(m_items[i].label)));
  }
  width += 2 * pad;
  //
  v_increment = fs->ascent + fs->descent + 2 * pad;
  height = v_increment * num_items;
  ascent = fs->ascent;
  //
  XFreeFontInfo(NULL, fs, 0);

  window = XCreateSimpleWindow(dpy, root,
       0, 0, width, height, 0,
       0, background_color);
  override_redirect(window);

  // Note: we get button events from existing pointer grab.
  XSelectInput(dpy, window, ExposureMask);
}
char* Vertical_Menu::name() { return menu_name; }
void Vertical_Menu::setName(const char* newName) {
  int len = strlen(newName);
  menu_name = (char*)realloc(menu_name, len + 1);
  strncpy(menu_name, newName, len);
  menu_name[len] = '\0';
}

void Vertical_Menu::beginning_interaction(pair<int, int> mouse_position, unsigned int button) {
  XMoveWindow(dpy, window,
  min(mouse_position.first, DisplayWidth(dpy, screen) - width),
  min(mouse_position.second, DisplayHeight(dpy, screen) - height));
}

void Vertical_Menu::handling(XEvent* e) {

  if (e->type == Expose) {
    draw(e->xexpose.x, e->xexpose.y);
  } else if (e->type == MotionNotify) {
    draw(e->xmotion.x, e->xmotion.y);
  } else if (e->type == ButtonPress || e->type == ButtonRelease) {
    unsigned int button = e->xbutton.button;
    int bx = e->xbutton.x;
    int by = e->xbutton.y;

    // Non-scroll ButtonPress outside ends, other ButtonPresses ignored.
    if (e->type == ButtonPress) {
      if (button != 4 && button != 5 && !contains(bx, by)) {
        end_interaction();
      }
    // Scroll ButtonRelease scrolls, others select (maybe not all should).
    } else {
      if (button == 4 || button == 5) {
        bool forward = button == 5;
        int i = item_index(by);
        if (i < 0 || i >= num_items) {
          i = forward ? 0 : num_items - 1;
        } else {
          i += forward ? 1 : -1;
          i = max(0, i);
          i = min(i, num_items - 1);
        }
        int x = max(0, bx);
        x = min(x, width - 1);
        int y = (int)((i + 0.5) * v_increment);
        warp_pointer(window, x, y);
      } else if (contains(bx, by)) {
        m_items[item_index(by)].action->act(pair<int, int>(bx, by), button);
        end_interaction();
      }
    }

  } // Can else here happen?

}

void Vertical_Menu::draw(int x, int y) {
  // Could optimize when item_index doesn't change.
  for (int i = 0; i < num_items; ++i) {
    XDrawString(dpy, window,
    contains(x, y) && i == item_index(y) ? highlight_gc : gc,
    pad, pad + ascent + i * v_increment,
    m_items[i].label, strlen(m_items[i].label));
  }
}

bool Vertical_Menu::contains(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; }
int Vertical_Menu::item_index(int y) { return y / v_increment; }

const unsigned int Vertical_Menu::size() { return num_items; }
const menu_item* Vertical_Menu::items() { return m_items; }


int Vertical_Menu::pad;


Invisible_Menu::Invisible_Menu() {
  window = XCreateWindow(dpy, root, 0, 0, 1, 1, 0,
       CopyFromParent, InputOnly, CopyFromParent, 0, NULL);
  override_redirect(window);
}


Stateful_Key::Stateful_Key(KeySym m,
      unsigned int button1, KeySym k1,
      unsigned int button2, KeySym k2) :
  m(m), k1(k1), k2(k2), b1(button1), b2(button2) {}

void Stateful_Key::beginning_interaction(pair<int, int> mouse_position, unsigned int button) {
  Invisible_Menu::beginning_interaction(mouse_position, button);
  fake_key(m, True);
  fake_key(button == b1 ? k1 : k2, True);
  fake_key(button == b1 ? k1 : k2, False);
}
void Stateful_Key::handling(XEvent* e) {
  if (e->type == ButtonRelease || e->type == ButtonPress) {
    unsigned int button = e->xbutton.button;
    if (button == b1 || button == b2) {
      if (e->type == ButtonRelease) {
        fake_key(button == b1 ? k1 : k2, True);
        fake_key(button == b1 ? k1 : k2, False);
      }
    } else {
      end_interaction();
    }
  } else if (e->xmotion.y != 0) {
    end_interaction();
  }
}
void Stateful_Key::end_interaction() {
  fake_key(m, False);
  Invisible_Menu::end_interaction();
}
const unsigned int Stateful_Key::button1() { return b1; }
const unsigned int Stateful_Key::button2() { return b2; }
const KeySym Stateful_Key::modifier() { return m; }
const KeySym Stateful_Key::key1() { return k1; }
const KeySym Stateful_Key::key2() { return k2; }


#ifdef OLD_CONFIG
Gesture_Menu::Gesture_Menu(Place place, menu_item* items, unsigned int num_items, unsigned int* cursor_shapes)
  : p(place), num_items(num_items), m_items(items), cursor_shapes(cursor_shapes) {}
const unsigned int* Gesture_Menu::shapes() { return cursor_shapes; }
#else
Gesture_Menu::Gesture_Menu(Place place, menu_item* items, unsigned int num_items, vector<string>& shape_names)
  : p(place), num_items(num_items), m_items(items), named_shapes(shape_names), mName(""), csName("") {
  init();
}
Gesture_Menu::Gesture_Menu(Place place, menu_item* items, unsigned int num_items,
        vector<string>& shape_names, const char* menuName, const char* cursorSetName)
  : p(place), num_items(num_items), m_items(items), named_shapes(shape_names) {
  int len;
  if (menuName) {
    len = strlen(menuName);
    mName = (char*)malloc(len + 1);
    strcpy(mName, menuName);
    mName[len] = '\0';
  } else
    mName = "";
  
  if (cursorSetName) {
    len = strlen(cursorSetName);
    csName = (char*)malloc(len + 1);
    strcpy(csName, cursorSetName);
    csName[len] = '\0';
  } else
    csName = "";
  
  init();
}
void Gesture_Menu::init() {
  cursor_shapes = (Cursor*)malloc(sizeof(Cursor) * named_shapes.size());
  for (unsigned int i = 0; i < named_shapes.size(); i++) {
    Cursor c = XcursorLibraryLoadCursor(dpy, named_shapes[i].c_str());
    if (!c) {
      cerr << "Cursor named " << named_shapes[i] << " does not exist in "
              << "cursorfont.h. Using plain arrow.";
      cursor_shapes[i] = XcursorLibraryLoadCursor(dpy, "arrow");
    } else
      cursor_shapes[i] = c;
  }
}
const vector<string> Gesture_Menu::shapes() { return named_shapes; }
char* Gesture_Menu::menuName() { return mName; }
void Gesture_Menu::setMenuName(const char* newName) {
  int len = strlen(newName);
  mName = (char*)realloc(mName, len + 1);
  strcpy(mName, newName);
  mName[len] = '\0';
}
char* Gesture_Menu::cursorSetName() { return csName; }
void Gesture_Menu::setCursorSetName(const char* newName) {
  int len = strlen(newName);
  csName = (char*)realloc(csName, len + 1);
  strncpy(csName, newName, len);
  csName[len] = '\0';
}

#endif

void Gesture_Menu::beginning_interaction(pair<int, int> mouse_position, unsigned int button) {
  update_cursor(0, 0);
  XMoveWindow(dpy, window, mouse_position.first, mouse_position.second);
}

void Gesture_Menu::handling(XEvent* e) {
  if (e->type == ButtonRelease) {
    int i = item_index(e->xbutton.x, e->xbutton.y);
    if (i >= 0) {
      m_items[i].action->act(pair<int, int>(e->xbutton.x, e->xbutton.y), e->xbutton.button);
    }
    end_interaction();
  } else if (e->type == MotionNotify) {
    update_cursor(e->xmotion.x, e->xmotion.y);
  }
}

void Gesture_Menu::update_cursor(int x, int y) {
  int i = item_index(x, y);
#ifdef OLD_CONFIG
  XDefineCursor(dpy, window, XCreateFontCursor(dpy, i >= 0 ? cursor_shapes[i] : neutral_cursor_shape));
#else
  XDefineCursor(dpy, window, i >= 0 ? cursor_shapes[i] : XCreateFontCursor(dpy, neutral_cursor_shape));
#endif
}

int Gesture_Menu::item_index(int x, int y) {
  int ox = x;
  if (p == TOP_RIGHT || p == RIGHT) {x = y; y = -ox;}
  if (p == BOTTOM_RIGHT || p == BOTTOM) {x = -x; y = -y;}
  if (p == BOTTOM_LEFT || p == LEFT) {x = -y; y = ox;}
  double arc = (p == LEFT || p == RIGHT || p == TOP || p == BOTTOM) ?  M_PI : M_PI / 2;
  return hypot(x, y) <= neutral_radius ? -1 : min((int)(atan2(y, x) / arc * num_items), num_items - 1);
}

const unsigned int Gesture_Menu::size() { return num_items; }
const Place Gesture_Menu::place() { return p; }
const menu_item* Gesture_Menu::items() { return m_items; }

unsigned int Gesture_Menu::neutral_radius;
unsigned int Gesture_Menu::neutral_cursor_shape;


// First effect in effects triggerable by button + mouse_position,
//  or NULL.
//
effect* applicable_effect(effect* effects, unsigned int num_effects,
			  pair<int, int> mouse_position, unsigned int button) {
  for (unsigned int i = 0; i < num_effects; ++i) {
    if (effects[i].t->applies(mouse_position, button)) {
      return effects + i;
    }
  }
  return NULL;
}

// Cover all places in effects triggers, with mapped, raised, InputOnly windows
//  accepting Button[Press|Release] events.
//
void make_interceptor_windows(effect* effects, unsigned int num_effects) {
  Place places[] = {TOP, RIGHT, BOTTOM, LEFT, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT};
  for (unsigned int i = 0; i < ARRAY_LENGTH(places); ++i) {
    Place p = places[i];
    for (unsigned int i = 0; i < num_effects; ++i) {
      if (effects[i].t->place().contains(p)) { 
	Window w = XCreateWindow(dpy, root,
				 p.position().first, p.position().second,
				 p.dimensions().first, p.dimensions().second,
				 0, 0, InputOnly, NULL, 0, 0);
	override_redirect(w);
	XSelectInput(dpy, w, ButtonPressMask | ButtonReleaseMask);
	XMapRaised(dpy, w);
	break;
      }
    }
  }
}

// call this before using any other classes
void configEdgy() {
  // Configuration of look.
  //
  unsigned long background_color = XBlackPixel(dpy, screen);
  unsigned long highlight_color = XWhitePixel(dpy, screen);
  unsigned long foreground_color = make_color("rgb:9/d/a");
  Vertical_Menu::pad = 2;
  char* font_name = "*schumacher*iso8859-1";
  Gesture_Menu::neutral_radius = 64;
  Gesture_Menu::neutral_cursor_shape = XC_circle;

  // Record configuration.
  //
  Menu::gc = XCreateGC(dpy, root, 0, NULL);
  Menu::highlight_gc = XCreateGC(dpy, root, 0, NULL);
  XSetForeground(dpy, Menu::gc, foreground_color);
  XSetBackground(dpy, Menu::gc, background_color);
  Menu::background_color = background_color;
  XSetForeground(dpy, Menu::highlight_gc, highlight_color);
  Menu::font = XLoadFont(dpy, font_name);
  XSetFont(dpy, Menu::gc, Menu::font);
  XSetFont(dpy, Menu::highlight_gc, Menu::font);
}

// we may want to have the main() somewhere else
#ifndef NO_BINARY_EDGY
int main(int argc, char **argv) {
  
  configEdgy();

#ifdef OLD_CONFIG
#define TRIGGER new Trigger

#define KEY new Key_Action
#define STATEFUL_KEY new Stateful_Key
#define COMMAND new System_Action

#define NO_ACTION new No_Action()

#define MENU_ITEMS(_name) menu_item items_ ## _name[] =
#define CURSORS(_name) unsigned int cursors_ ## _name[] =
//#define CURSORS(_name) char* cursors_ ## _name[] =

#define GESTURES(_place, _items, _cursors) new Gesture_Menu(_place, items_ ## _items, \
                 sizeof(items_ ## _items) / sizeof(menu_item), cursors_ ## _cursors)
#define MENU(_items) new Vertical_Menu(items_ ## _items, sizeof(items_ ## _items) / sizeof(menu_item))

#define EFFECTS effect effects[] =

#include "configuration.in"
#else
  
  // get the filename from the command line. if it's not there,
  // use configuration.yml
  string filename = "configuration.yml";
  bool expectingFilename = false;
  string s;
  for (int i = 0; i < argc; i++) {
    s = argv[i];
    if (s == "-h" || s == "--help") {
      cout << "Usage: edgy [-c configfile]\n";
      cout << "configfile should be a path to a YAML configuration file.\n";
      cout << "    If configfile is not provided, edgy will try to use configuration.yml in" <<
              " the current directory.\n";
      return 0;
    } else if (s == "-c") {
      expectingFilename = true;
      continue;
    }
    if (expectingFilename) {
      filename = s;
      break;
    }
  }
  
  ConfigParser* cp = new ConfigParser(filename.c_str());
  if (cp->ioerror) {
    delete cp;
    return EXIT_FAILURE;
  }
  bool parsed = cp->parse();
  if (!parsed) {
    cerr << "Could not parse the config file. Exiting.";
    delete cp;
    return EXIT_FAILURE;
  }
  configdata* cd = cp->getConfig();
  effect effects[cd->effects.size()];
  for (unsigned int i = 0; i < cd->effects.size(); i++)
    effects[i] = *cd->effects[i];
#endif
  
  make_interceptor_windows(effects, ARRAY_LENGTH(effects));

  while (true) {

    // Standard event loop.
    //   Events from
    //    interceptor windows (Button[Press|Release])
    //    active menu window and pointer grab on it
    //
    XEvent event;
    XNextEvent(dpy, &event);

    // An active menu gets event (ignores ones it's not grabbing).
    //  Menus also (de)register themselves as active.
    if (Menu::active_menu) {
      Menu::active_menu->handle(&event);
    } else {
      //
      // Extract button + pointer information.
      //
      //   [Choosing between pointer, reference, value copy:
      //      big / change existing object / special value / repoint / array arithmetic (array element): pointer
      //      big / change existing object / no special / no repoint / no array arithmetic: reference
      //      small / don't change existing / no special / repoint / no array arithmetic: copy]
      //
      const XButtonEvent& be = event.xbutton;
      pair<int, int> mouse_position(be.x_root, be.y_root);
      int button = be.button;
      //
      // Check for associated effect.
      //   [Worry about effects data structuring after Trigger + Action interaction understood]
      //
      effect* ef = applicable_effect(effects, ARRAY_LENGTH(effects), mouse_position, button);
      if (ef) {
        if (event.type == ButtonRelease) {
          ef->a->act(mouse_position, button);
          if (Menu::active_menu) {
            Menu::active_menu->begin_interaction(mouse_position, button);
          }
        }
      } else if (event.type == ButtonPress) {
        pair<int, int> delta(interior_delta(mouse_position));
        XTestFakeButtonEvent(dpy, button, False, CurrentTime);
        warp_pointer(None, delta.first, delta.second);
        XTestFakeButtonEvent(dpy, button, True, CurrentTime);
      }
    }
  }

  return EXIT_FAILURE; // shouldn't get here!  

}
#endif
